package com.unimod.fsml.model;

import java.io.IOException;
import java.io.InputStream;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.core.resources.IFile;
import org.eclipse.jdt.launching.SocketUtil;

import com.evelopers.common.exception.CommonException;
import com.evelopers.common.exception.SystemException;
import com.evelopers.unimod.compilation.StateMachineCompiler;
import com.evelopers.unimod.core.stateworks.Event;
import com.evelopers.unimod.core.stateworks.Model;
import com.evelopers.unimod.core.stateworks.StateMachine;
import com.evelopers.unimod.debug.app.AppDebugger;
import com.evelopers.unimod.debug.protocol.JavaSpecificMessageCoder;
import com.evelopers.unimod.plugin.eclipse.model.GModel;
import com.evelopers.unimod.runtime.ModelEngine;
import com.evelopers.unimod.runtime.context.StateMachineContext;
import com.evelopers.unimod.runtime.context.StateMachineContextImpl;
import com.evelopers.unimod.runtime.interpretation.InterpretationHelper;
import com.evelopers.unimod.runtime.logger.ShortLogger;
import com.evelopers.unimod.transform.xml.XMLToModel;

/**
 * This class contains model of FSML parser automata and
 * provides methods for all who need this model.
 * Singleton.
 * 
 * @author Ivan Lagunov
 */
public class FSMLModelHelper {
    private static final Log log = LogFactory.getLog(FSMLModelHelper.class);
	private static final String FSML_MODEL_FILE = "com/unimod/fsml/model/fsml.xml";

    private static FSMLModelHelper modelHelper;
    
    /**
     * Model of fsml parser automata.
     */
    private Model fsmlModel;
    /**
     * Map with controlled objects.
     */
	private ParserCOMap coMap;
	/**
	 * Engine for fsml model.
	 */
	private ModelEngine engine;
	/**
	 * Model of input program automata.
	 */
	private GModel model;
	/**
	 * Context of current state machine.
	 */
	private StateMachineContext smContext;
	/**
	 * Corresponding UniMod xml file descriptor.
	 */
	private IFile uniModXml;

    private boolean debug = false;
    private AppDebugger debugger;
    
	private FSMLModelHelper() {
        try {
			fsmlModel = readModel(FSML_MODEL_FILE);
		} catch (Exception e) {
			log.fatal("Cannot load FSML parser", e);
		}
	}

	/**
	 * Returns instance of FSML model helper.
	 * 
	 * @return instance of FSML model helper.
	 */
	public static FSMLModelHelper getInstance() {
		if (null == modelHelper) {
			modelHelper = new FSMLModelHelper();
		}
		return modelHelper;
	}
	
	/**
	 * Returns graphic model of automaton for current FSML program.
	 * 
	 * @return graphic model of automaton for current FSML program.
	 */
    public GModel getProgramModel() {
		return model;
	}

	public ParserCOMap getCOMap() {
		return coMap;
	}

	public ModelEngine getEngine() {
		return engine;
	}
	
	public StateMachineContext getStateMachineContext() {
		return smContext;
	}

	public void setDebug(boolean debug) {
		this.debug = debug;
	}
	
	public IFile getUniModXml() {
		return uniModXml;
	}

	public void setUniModXml(IFile uniModXml) {
		this.uniModXml = uniModXml;
	}

	public void prepareAutoCompletion(String programSource, int offset) throws CommonException {
		rebuildProgramModel(programSource, offset);
	}
	
	public void finishAutoCompletion() {
		finishProcessingEvents();
	}
	
	public void prepareValidation(String programSource) throws CommonException {
		rebuildProgramModel(programSource, programSource.length() + 1);
	}
	
	private void rebuildProgramModel(String programSource, int offset) throws CommonException {
		smContext = StateMachineContextImpl.create();
		resetModel(smContext);
    	coMap.getLexer().initData(programSource, offset);
		processEvents(smContext);
	}
	
	/**
	 * Resets FSML program model.
	 * 
	 * @param model
	 * @throws CommonException
	 */
	private void resetModel(StateMachineContext smContext) throws CommonException {
		this.model = new GModel();
		coMap = new ParserCOMap(fsmlModel.getRootStateMachine(), model, smContext);
		engine = setupEngine(fsmlModel, coMap);
	}
	
	/**
	 * Sends events made by lexer to FSML automaton until the specified offset.
	 * 
	 * @param smContext
	 * @param offset
	 * @throws CommonException
	 */
    private void processEvents(StateMachineContext smContext) throws CommonException {
        if (debug) {
            addDebugger(engine);
        }

        engine.start();
        engine.getEventManager().handle(Event.ANY, smContext);
        
        boolean finished = coMap.getLexer().processTokensBeforeOffset();
        
		if (debug && finished) {
            debugger.stop();
        }
    }

    private void finishProcessingEvents() {
        coMap.getLexer().processTokensAfterOffset();
        
		if (debug) {
            debugger.stop();
        }
    }
    
    private Model readModel(String modelFile) throws SystemException, IOException {
        InputStream is = getClass().getClassLoader().getResourceAsStream(modelFile);
        Model model = XMLToModel.load(is);
        is.close();
        resolveUnhandledTokens(model.getRootStateMachine());
        
        return model; 
    }

    private void resolveUnhandledTokens(StateMachine stateMachine) throws SystemException {     
        UnhandledTokensResolver utr = UnhandledTokensResolver.getInstance();
        
        StateMachineCompiler compiler = new StateMachineCompiler(null);
        compiler.compileWithIncluded(stateMachine);
        utr.resolve(stateMachine);      
        compiler.compileWithIncluded(stateMachine);
    }

    private ModelEngine setupEngine(Model model, ParserCOMap coMap) throws CommonException {
        InterpretationHelper helper = InterpretationHelper.getInstance(); 
        ModelEngine engine = helper.createBuildInModelEngine(model, coMap, false);
        
        //engine.getEventProcessor().addEventProcessorListener(new ShortLogger(Logger.CONSOLE));
        
        coMap.getLexer().init(engine);

        return engine;
    }

    private void addDebugger(ModelEngine engine) throws CommonException {
        try {
            engine.getEventProcessor().addEventProcessorListener(new ShortLogger(LogFactory.getLog(this.getClass())));

            int port = SocketUtil.findFreePort();
            if (port != -1) {
                System.out.println("port: " + port);
                debugger = new AppDebugger(port, true, new JavaSpecificMessageCoder(), engine);
                debugger.start();
            } else {
                System.out.println("No free port");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
